package service

import (
	"context"
	"fmt"
	"github.com/pkg/errors"
	"gorm.io/gorm"
	"inventory/api/inventory"
	"inventory/internal/constant"
	"time"

	"inventory/internal/model"
	"inventory/pkg/ecode"
)

// GetMaterialType 获取物料配置类别
func (s *Service) GetMaterialType(ctx context.Context, req *inventory.CommonRequest) (resp *inventory.GetMaterialTypeResponse, err error) {
	sgId := req.WebToken.SgId
	if err = s.downstream.CheckAdmin(ctx, req.WebToken, req.PStoreId); err != nil {
		return
	}
	var materialTypes []*model.MaterialType
	if materialTypes, err = s.dao.FindAllMaterialTypes(ctx, sgId, req.PStoreId); err != nil {
		return
	}
	ref := make(map[uint32][]uint32)
	idRef := make(map[uint32]*model.MaterialType)
	for _, t := range materialTypes {
		if t.ParentID != 0 {
			if subs, ok := ref[t.ParentID]; !ok {
				subs = []uint32{t.ID}
				ref[t.ParentID] = subs
			} else {
				ref[t.ParentID] = append(ref[t.ParentID], t.ID)
			}
		}
		idRef[t.ID] = t
	}
	views := make([]*inventory.MaterialTypeView, 0)
	for _, t := range materialTypes {
		if t.ParentID == 0 {
			var subs []*inventory.MaterialTypeView
			if v, ok := ref[t.ID]; ok {
				for _, m := range v {
					subs = append(subs, &inventory.MaterialTypeView{
						Id:   m,
						Name: idRef[m].Name,
						Code: idRef[m].Code,
					})
				}
			}
			views = append(views, &inventory.MaterialTypeView{
				Subs: subs,
				Id:   t.ID,
				Name: t.Name,
				Code: t.Code,
			})
		}
	}

	resp = &inventory.GetMaterialTypeResponse{
		Types: views,
	}
	return

}

// AddMaterialType 新建产品配置类别
func (s *Service) AddMaterialType(ctx context.Context, req *inventory.AddMaterialTypeRequest) (resp *inventory.DefaultResp, err error) {
	empId, sgId := req.WebToken.EmpId, req.WebToken.SgId
	if err = s.downstream.CheckAdmin(ctx, req.WebToken, req.PStoreId); err != nil {
		return
	}
	resp = &inventory.DefaultResp{
		Status: true,
	}
	err = s.dao.Transaction(ctx, func(ctx context.Context) error {
		now := time.Now()
		level := 1
		if req.ParentId > 0 {
			if _, err = s.dao.GetMaterialType(ctx, sgId, req.PStoreId, req.ParentId); err != nil {
				if errors.Cause(err) == gorm.ErrRecordNotFound {
					err = constant.ErrInvalidMaterialType
				}
				return err
			}
			level = 2
		}
		materialType := &model.MaterialType{
			SgID:       sgId,
			PStoreID:   req.PStoreId,
			Name:       req.Name,
			ParentID:   req.ParentId,
			Level:      uint8(level),
			Status:     constant.MaterialTypeValid,
			Operator:   empId,
			CreateTime: now,
			UpdateTime: now,
		}
		var isExist bool
		if isExist, err = s.dao.CheckExistMaterialTypeName(ctx, sgId, req.PStoreId, req.ParentId, req.Name); err != nil {
			return err
		}
		if isExist {
			return ecode.BadRequest(fmt.Sprintf("类别名称: %s 与已有类别名称重复", req.Name))
		}
		var code string
		if code, err = s.dao.GenerateMaterialTypeCode(ctx, sgId, req.PStoreId, req.ParentId); err != nil {
			return err
		}
		materialType.Code = code
		if err = s.dao.AddMaterialType(ctx, materialType); err != nil {
			return err
		}
		return nil
	})
	return
}

// EditMaterialType 修改产品配置类别
func (s *Service) EditMaterialType(ctx context.Context, req *inventory.EditMaterialTypeRequest) (resp *inventory.DefaultResp, err error) {
	empId, sgId := req.WebToken.EmpId, req.WebToken.SgId
	resp = &inventory.DefaultResp{
		Status: true,
	}
	if err = s.downstream.CheckAdmin(ctx, req.WebToken, req.PStoreId); err != nil {
		return
	}
	err = s.dao.Transaction(ctx, func(ctx context.Context) error {
		materialType := model.MaterialType{}
		if err = s.dao.Lock(ctx, model.MaterialType{SgID: sgId, PStoreID: req.PStoreId, ID: req.Id},
			&materialType); err != nil {
			if errors.Cause(err) == gorm.ErrRecordNotFound {
				err = constant.ErrMaterialTypeNotExist
			}
			return err
		}

		if materialType.Name != req.Name {
			var isExist bool
			if isExist, err = s.dao.CheckExistMaterialTypeName(
				ctx, sgId, req.PStoreId, materialType.ParentID, req.Name); err != nil {
				return err
			}
			if isExist {
				return ecode.BadRequest(fmt.Sprintf("类别名称: %s 与已有类别名称重复", req.Name))
			}
			materialType.Name = req.Name
		}
		materialType.UpdateTime = time.Now()
		materialType.Operator = empId
		if err = s.dao.UpdateMaterialType(ctx, &materialType); err != nil {
			return err
		}
		return nil
	})
	return
}

// DeleteMaterialType 删除产品配置类别
func (s *Service) DeleteMaterialType(ctx context.Context, req *inventory.CommonObjectRequest) (resp *inventory.DefaultResp, err error) {
	empId, sgId := req.WebToken.EmpId, req.WebToken.SgId
	resp = &inventory.DefaultResp{
		Status: true,
	}
	if err = s.downstream.CheckAdmin(ctx, req.WebToken, req.PStoreId); err != nil {
		return
	}
	err = s.dao.Transaction(ctx, func(ctx context.Context) error {
		materialType := model.MaterialType{}
		if err = s.dao.Lock(ctx, model.MaterialType{SgID: sgId, PStoreID: req.PStoreId, ID: req.Id},
			&materialType); err != nil {
			if errors.Cause(err) == gorm.ErrRecordNotFound {
				err = constant.ErrMaterialTypeNotExist
			}
			return err
		}
		var hasChild bool
		if materialType.ParentID == 0 {
			if hasChild, err = s.dao.CheckExistChildMaterialType(ctx, sgId, req.PStoreId, req.Id); err != nil {
				return err
			}
			if hasChild {
				return ecode.BadRequest("删除类别: %s 失败, 原因: 存在子类别, 请删除所有子类别后重试", materialType.Name)
			}
		} else {
			if hasChild, err = s.dao.CheckExistChildMaterial(ctx, sgId, req.PStoreId, req.Id); err != nil {
				return err
			}
			if hasChild {
				return ecode.BadRequest("删除类别: %s 失败, 原因: 存在子物料, 请删除所有子物料后重试", materialType.Name)
			}
		}
		if err = s.dao.DeleteMaterialType(ctx, sgId, req.PStoreId, empId, req.Id); err != nil {
			return err
		}
		return nil
	})
	return
}

// GetMaterials 获取所有物料
func (s *Service) GetMaterials(ctx context.Context, req *inventory.CommonObjectRequest) (resp *inventory.GetMaterialsResponse, err error) {
	sgId := req.WebToken.SgId
	if err = s.downstream.CheckAdmin(ctx, req.WebToken, req.PStoreId); err != nil {
		return
	}
	var (
		pMaterialType, rawMaterialType *model.MaterialType
		parentTypeIDs                  []uint32
	)
	if rawMaterialType, err = s.dao.GetMaterialType(ctx, sgId, req.PStoreId, req.Id); err != nil {
		if errors.Cause(err) == gorm.ErrRecordNotFound {
			err = ecode.BadRequest("无效的类别ID")
		}
		return
	}
	parentMap := make(map[uint32]*model.MaterialType)
	if rawMaterialType.Level == 2 {
		if pMaterialType, err = s.dao.GetMaterialType(ctx, sgId, req.PStoreId, rawMaterialType.ParentID); err != nil {
			return
		}
		parentMap[rawMaterialType.ID] = rawMaterialType
		parentTypeIDs = append(parentTypeIDs, rawMaterialType.ID)
	} else {
		pMaterialType = rawMaterialType
		var parentTypes []*model.MaterialType
		if parentTypes, err = s.dao.FindMaterialTypeByParentID(ctx, sgId, rawMaterialType.ID); err != nil {
			return
		}
		for _, parentType := range parentTypes {
			parentMap[parentType.ID] = parentType
			parentTypeIDs = append(parentTypeIDs, parentType.ID)
		}
	}

	var materials []*model.Material
	if materials, err = s.dao.FindMaterialsByTypes(ctx, sgId, req.PStoreId, parentTypeIDs); err != nil {
		return
	}
	views := make([]*inventory.MaterialView, len(materials))
	for i, m := range materials {
		views[i] = &inventory.MaterialView{
			Id:     m.ID,
			Name:   m.Name,
			Code:   fmt.Sprintf("%s-%s-%s", pMaterialType.Code, parentMap[m.MtID].Code, m.Code),
			TypeId: m.MtID,
		}
	}
	resp = &inventory.GetMaterialsResponse{Materials: views}
	return
}

// GetMaterialDetail 获取物料详情
func (s *Service) GetMaterialDetail(ctx context.Context, req *inventory.CommonObject64Request) (
	resp *inventory.GetMaterialDetailResponse, err error) {
	sgId := req.WebToken.SgId
	if err = s.downstream.CheckAdmin(ctx, req.WebToken, req.PStoreId); err != nil {
		return
	}
	var material *model.Material
	if material, err = s.dao.GetMaterial(ctx, sgId, req.PStoreId, req.Id); err != nil {
		if errors.Cause(err) == gorm.ErrRecordNotFound {
			err = constant.ErrMaterialNotExist
		}
		return
	}

	var pMaterialType, materialType *model.MaterialType
	if materialType, err = s.dao.GetMaterialType(ctx, sgId, req.PStoreId, material.MtID); err != nil {
		if errors.Cause(err) == gorm.ErrRecordNotFound || materialType.Level != 2 {
			err = constant.ErrInvalidMaterialType
		}
		return
	}
	if pMaterialType, err = s.dao.GetMaterialType(ctx, sgId, req.PStoreId, materialType.ParentID); err != nil {
		return
	}

	unitIDs := []uint32{material.Unit, material.StorageUnit, material.PurchaseUnit}
	var units []*model.Unit
	if units, err = s.dao.FindUnitsByIDs(ctx, sgId, req.PStoreId, unitIDs); err != nil {
		return
	}
	um := make(map[uint32]*model.Unit, len(units))
	for _, u := range units {
		um[u.ID] = u
	}

	resp = &inventory.GetMaterialDetailResponse{
		Id:   material.ID,
		Name: material.Name,
		Code: fmt.Sprintf("%s-%s-%s", pMaterialType.Code, materialType.Code, material.Code),
		Unit: &inventory.UnitView{
			Id:   material.Unit,
			Name: um[material.Unit].Name,
		},
		PurchaseUnit: &inventory.UnitView{
			Id:    material.PurchaseUnit,
			Name:  um[material.PurchaseUnit].Name,
			Ratio: material.PurchaseRatio / material.StorageRatio, // 这里前端展示采购/库存换算比
		},
		StorageUnit: &inventory.UnitView{
			Id:    material.StorageUnit,
			Name:  um[material.StorageUnit].Name,
			Ratio: material.StorageRatio,
		},
	}
	return
}

func (s *Service) checkUnit(ctx context.Context, unit *inventory.UnitView, now time.Time, hit string,
	sgId, pStoreId, empId uint32) (err error) {
	var isExist bool
	if unit.Id > 0 {
		if isExist, err = s.dao.CheckExist(ctx, &model.Unit{}, model.Unit{
			SgID:     sgId,
			PStoreID: pStoreId,
			ID:       unit.Id,
			Status:   constant.UnitValid,
		}); err != nil {
			return err
		}
		if !isExist {
			return ecode.BadRequest("所选%s单位不合法", hit)
		}
	} else {
		var matchUnit *model.Unit
		if matchUnit, err = s.dao.GetUnitByName(ctx, sgId, pStoreId, unit.Name); err != nil {
			if errors.Cause(err) == gorm.ErrRecordNotFound {
				err = nil
				matchUnit = &model.Unit{
					Name:       unit.Name,
					Status:     constant.UnitValid,
					Operator:   empId,
					SgID:       sgId,
					PStoreID:   pStoreId,
					CreateTime: now,
					UpdateTime: now,
				}
				if err = s.dao.Create(ctx, matchUnit); err != nil {
					return err
				}
				unit.Id = matchUnit.ID
			} else {
				return
			}
		} else {
			// 允许直接匹配已存在的单位
			unit.Id = matchUnit.ID
		}
	}
	return
}

// AddMaterial 新建产品
func (s *Service) AddMaterial(ctx context.Context, req *inventory.AddMaterialRequest) (resp *inventory.DefaultResp, err error) {
	empId, sgId := req.WebToken.EmpId, req.WebToken.SgId
	if err = s.downstream.CheckAdmin(ctx, req.WebToken, req.PStoreId); err != nil {
		return
	}
	resp = &inventory.DefaultResp{
		Status: true,
	}
	err = s.dao.Transaction(ctx, func(ctx context.Context) error {
		_, err = s.dao.GetMaterialType(ctx, sgId, req.PStoreId, req.ParentId)
		if err != nil {
			if errors.Cause(err) == gorm.ErrRecordNotFound {
				err = constant.ErrInvalidMaterialType
			}
			return err
		}
		now := time.Now()
		// 前置检查
		if err = s.checkUnit(ctx, req.Unit, now, "零售", sgId, req.PStoreId, empId); err != nil {
			return err
		}
		if err = s.checkUnit(ctx, req.PurchaseUnit, now, "采购", sgId, req.PStoreId, empId); err != nil {
			return err
		}
		if err = s.checkUnit(ctx, req.StorageUnit, now, "库存", sgId, req.PStoreId, empId); err != nil {
			return err
		}
		var isExist bool
		if isExist, err = s.dao.CheckExist(ctx, &model.Material{}, model.Material{
			SgID:     sgId,
			PStoreID: req.PStoreId,
			MtID:     req.ParentId,
			Name:     req.Name,
			Status:   constant.MaterialValid,
		}); err != nil {
			return err
		}
		if isExist {
			return ecode.BadRequest("物料名称:%s已存在", req.Name)
		}
		material := model.Material{
			Name:          req.Name,
			MtID:          req.ParentId,
			Status:        constant.MaterialValid,
			Unit:          req.Unit.Id,
			PurchaseUnit:  req.PurchaseUnit.Id,
			PurchaseRatio: req.PurchaseUnit.Ratio * req.StorageUnit.Ratio, // 这里展示库存
			StorageUnit:   req.StorageUnit.Id,
			StorageRatio:  req.StorageUnit.Ratio,
			Operator:      empId,
			SgID:          sgId,
			PStoreID:      req.PStoreId,
			CreateTime:    now,
			UpdateTime:    now,
		}
		var code string
		if code, err = s.dao.GenerateMaterialCode(ctx, sgId, req.PStoreId, material.MtID); err != nil {
			return err
		}
		material.Code = code
		if err = s.dao.Create(ctx, &material); err != nil {
			return err
		}
		return nil
	})
	return
}

// EditMaterial 修改物料
func (s *Service) EditMaterial(ctx context.Context, req *inventory.EditMaterialRequest) (resp *inventory.DefaultResp, err error) {
	empId, sgId := req.WebToken.EmpId, req.WebToken.SgId
	resp = &inventory.DefaultResp{
		Status: true,
	}
	if err = s.downstream.CheckAdmin(ctx, req.WebToken, req.PStoreId); err != nil {
		return
	}
	err = s.dao.Transaction(ctx, func(ctx context.Context) error {
		now := time.Now()
		// 前置检查
		if err = s.checkUnit(ctx, req.Unit, now, "零售", sgId, req.PStoreId, empId); err != nil {
			return err
		}
		if err = s.checkUnit(ctx, req.PurchaseUnit, now, "采购", sgId, req.PStoreId, empId); err != nil {
			return err
		}
		if err = s.checkUnit(ctx, req.StorageUnit, now, "库存", sgId, req.PStoreId, empId); err != nil {
			return err
		}
		// 获取物料
		material := model.Material{}
		if err = s.dao.Lock(ctx, model.Material{
			ID:       req.Id,
			SgID:     sgId,
			PStoreID: req.PStoreId,
			Status:   constant.MaterialTypeValid,
		}, &material); err != nil {
			if errors.Cause(err) == gorm.ErrRecordNotFound {
				err = ecode.BadRequest("ID为: %d 的物料不存在", req.Id)
			}
			return err
		}
		material.Unit = req.Unit.Id
		material.PurchaseUnit = req.PurchaseUnit.Id
		material.StorageUnit = req.StorageUnit.Id
		material.PurchaseRatio = req.PurchaseUnit.Ratio * req.StorageUnit.Ratio // 前端传入purchaseRatio为采购库存换算比
		material.StorageRatio = req.StorageUnit.Ratio
		material.Operator = empId
		material.UpdateTime = time.Now()
		if err = s.dao.Save(ctx, material); err != nil {
			return err
		}
		return nil
	})
	return
}

// DeleteMaterial 删除物料
func (s *Service) DeleteMaterial(ctx context.Context, req *inventory.CommonObject64Request) (resp *inventory.DefaultResp, err error) {
	empId, sgId := req.WebToken.EmpId, req.WebToken.SgId
	resp = &inventory.DefaultResp{
		Status: true,
	}
	if err = s.downstream.CheckAdmin(ctx, req.WebToken, req.PStoreId); err != nil {
		return
	}
	err = s.dao.Transaction(ctx, func(ctx context.Context) (err error) {
		material := model.Material{}
		if err = s.dao.Lock(ctx, model.Material{
			ID:       req.Id,
			SgID:     sgId,
			PStoreID: req.PStoreId,
			Status:   constant.MaterialTypeValid,
		}, &material); err != nil {
			if errors.Cause(err) == gorm.ErrRecordNotFound {
				err = ecode.BadRequest("ID为: %d 的物料不存在", req.Id)
			}
			return
		}
		var boms []*model.BomConfig
		if boms, err = s.dao.FindBomByMaterialIds(ctx, []uint64{req.Id}); err != nil {
			return
		}
		if len(boms) != 0 {
			err = ecode.BadRequest("物料存在于bom配置中，需要先删除bom再删除物料")
			return
		}
		var storages []*model.Storage
		if storages, err = s.dao.FindStoragesByMaterialIDs(ctx, []uint64{req.Id}); err != nil {
			return
		}
		if len(storages) != 0 {
			for _, s := range storages {
				if s.Quantity > 0 || s.FreezeQuantity > 0 {
					err = ecode.BadRequest("物料存在于部分仓库实时库存&冻结库存大于0，请先完成发货&盘空后再删除")
					return
				}
			}
		}
		if err = s.dao.Update(ctx, &material, map[string]interface{}{
			"sg_id":       sgId,
			"p_store_id":  int(req.PStoreId),
			"operator":    empId,
			"status":      nil,
			"update_time": time.Now(),
		}); err != nil {
			return
		}
		// 删除库存行
		if err = s.dao.DeleteStorageByStorageKey(ctx, fmt.Sprintf("m-%d", req.Id)); err != nil {
			return
		}
		return
	})
	return
}

// GetUnits 获取物料单位列表
func (s *Service) GetUnits(ctx context.Context, req *inventory.CommonRequest) (resp *inventory.GetUnitsResponse, err error) {
	sgId := req.WebToken.SgId
	if err = s.downstream.CheckAdmin(ctx, req.WebToken, req.PStoreId); err != nil {
		return
	}
	var units []*model.Unit
	if units, err = s.dao.FindUnits(ctx, sgId, req.PStoreId); err != nil {
		return
	}
	views := make([]*inventory.UnitView, len(units))
	for i, u := range units {
		views[i] = &inventory.UnitView{Id: u.ID, Name: u.Name}
	}
	resp = &inventory.GetUnitsResponse{Units: views}
	return
}

// DeleteUnit 删除物料单位
func (s *Service) DeleteUnit(ctx context.Context, req *inventory.CommonObjectRequest) (resp *inventory.DefaultResp, err error) {
	empId, sgId := req.WebToken.EmpId, req.WebToken.SgId
	if err = s.downstream.CheckAdmin(ctx, req.WebToken, req.PStoreId); err != nil {
		return
	}
	err = s.dao.Transaction(ctx, func(ctx context.Context) (err error) {
		unit := model.Unit{}
		if err = s.dao.Lock(ctx, model.Unit{
			ID:       req.Id,
			SgID:     sgId,
			PStoreID: req.PStoreId,
			Status:   constant.MaterialTypeValid,
		}, &unit); err != nil {
			if errors.Cause(err) == gorm.ErrRecordNotFound {
				err = ecode.BadRequest("ID为: %d 的物料单位不存在", req.Id)
			}
			return
		}
		var exist bool
		if exist, err = s.dao.CheckExistMaterialUnit(ctx, sgId, req.PStoreId, req.Id); err != nil {
			return
		}
		if exist {
			err = ecode.BadRequest("所选单位正在被物料使用中")
			return
		}
		if err = s.dao.Update(ctx, &unit, map[string]interface{}{
			"sg_id":       sgId,
			"p_store_id":  int(req.PStoreId),
			"operator":    empId,
			"status":      nil,
			"update_time": time.Now(),
		}); err != nil {
			return
		}
		return
	})
	return
}
